import * as Y from 'yjs'
import { WebsocketProvider } from 'y-websocket'
import {
  IKeydown,
  IRangeStyle,
  IWebsocketProviderStatus,
  IYdocEventObserve
} from '../../interface/Websocket'
import { EventBus } from '../event/eventbus/EventBus'
import { EventBusMap } from '../../interface/EventBus'
import { Command } from '../command/Command'
import { IEditorData } from '../../interface/Editor'

export class Ydoc extends EventBus<EventBusMap> {
  private ydoc: Y.Doc
  private ymap: Y.Map<unknown>
  private provider: any | undefined
  private connect: boolean | undefined
  private url: string
  private roomname: string
  private command: Command
  private userid: string
  private color: string | undefined

  constructor(
    url: string,
    roomname: string,
    userid: string,
    command: Command,
    color?: string
  ) {
    super()
    this.color = color
    this.url = url
    this.roomname = roomname
    this.connect = false
    this.command = command
    this.userid = userid

    // 创建 YDoc 文档
    this.ydoc = new Y.Doc()

    this.ymap = this.ydoc.getMap('map')

    // 【方案二】 websocket 方式实现协同（已自己搭建 websocket 服务）
    this.provider = new WebsocketProvider(this.url, this.roomname, this.ydoc)

    // 监听链接状态F·
    this.provider.on('status', (event: IWebsocketProviderStatus) => {
      const { status } = event
      if (status === 'connected') this.connect = true
      else this.connect = false
    })

    this.ymap.observe((event, transaction) =>
      this.YMapObserve(event, transaction)
    )
  }

  // YMap Observe
  public YMapObserve(
    { changes }: Y.YMapEvent<unknown>,
    Transaction: Y.Transaction
  ) {
    if (Transaction.local) return // 本地触发的 observer 不需要观察

    changes.keys.forEach((_change, key) => {
      const event = key.split('_')[0]
      const params = <IYdocEventObserve>this.ymap.get(key)

      switch (event) {
        // 用户登录
        case 'userlogin':
          // 此处事件应该通过 event 传递到 instance.listener.userLogin=()=>{} 通过 eventBus 监听  this.ymap.get(key)
          break

        // 用户设置选区
        //  no-case-declarations
        case 'userRange':
          /**
           *  startIndex: number
           *  endIndex: number
           *  id: string
           *  payload?: string
           */
          this.command.setUserRange({
            startIndex: <number>params.startIndex,
            endIndex: <number>params.endIndex,
            id: <string>params.id,
            payload: <string>params.color
          })
          break

        // 用户退出销毁编辑器
        case 'destroy':
          this.command.setUserUnRange(0, 0, {
            userid: <string>this.ymap.get(key)
          })
          break

        // 用户内容区变化
        case 'contentChange':
          this.command.executeContentChange(
            <IEditorData>this.ymap.get('contentChange')
          )
          break

        case 'keydown':
          this.command.executeKeydown(<IKeydown>this.ymap.get('keydown'))
          break

        case 'rangeStyle':
          this.command.executeRangeStyle(
            <IRangeStyle>this.ymap.get('rangeStyle')
          )
          break

        default:
          console.log('YMapObserve default eventHandle.')
          break
      }
    })
  }

  // 关闭连接方法
  public disConnection() {
    if (!this.connect) return
    this.provider.disconnect()
  }

  // index.ts 中初始化 canvas 调用 initUser 调用 userlogin 事件
  public userInitEditor(username: string, userid: string) {
    this.ymap.set(`userlogin_${this.userid}`, {
      username,
      userid,
      color: this.color
    })
  }

  // 用户选区操作
  public setUserRange(payload: IYdocEventObserve) {
    this.ymap.set(`userRange_${this.userid}`, {
      startIndex: payload.startIndex,
      endIndex: payload.endIndex,
      isCrossRowCol: payload.isCrossRowCol,
      userid: payload.userid,
      color: this.color
    })
  }

  // 调用 destroy 销毁后，需要通知其他用户取消选区
  public canvasDestroy() {
    this.ymap.set('destroy', this.userid)
  }

  // 实现用户输入收集 Draw input 事件实现
  public collectUserInput(data: IEditorData) {
    this.ymap.set('contentChange', data)
  }

  // 用户按键映射
  public keydownHandle(payload: IKeydown) {
    this.ymap.set('keydown', payload)
  }

  // 广播用户样式修改
  public rangeStyleChange(payload: IRangeStyle) {
    this.ymap.set('rangeStyle', payload)
  }
}
